﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using NAudio.Dsp;

namespace Orpheus.Analyzer
{
    public class CombFilterWithBandsTempoAnalyzer: TrackAnalyserBase
    {
        private List<Tuple<float, float>> _samples;
        private bool _calculated = false;

        public CombFilterWithBandsTempoAnalyzer(TrackInfo trackInfo, GetCurrentTimeDelegate getCurrentTimeCallback) 
            : base(trackInfo, getCurrentTimeCallback)
        {
            _samples = new List<Tuple<float, float>>();
            _trackInfo.Tempo = new TimeStepFunction<double>();
        }

        bool IsPowerOfTwo(int x)
        {
            return (x & (x - 1)) == 0;
        }

        public override void ProcessSamples(float left, float right)
        {
            _samples.Add(new Tuple<float, float>(left, right));
            int m = (int) Math.Log(_samples.Count, 2);
            if (_GetCurrentTimeCallback().TotalSeconds > 5  && IsPowerOfTwo(_samples.Count) 
                && _samples.Count % 16 == 0 && !_calculated)
            {
                int maxFreq = 4096;
                int[] bandLimits = {0, 200, 400, 800, 1600, 3200};
                var a = FilterBank(_samples, bandLimits, maxFreq);
                var b = HWindow(a, 0.2, bandLimits, maxFreq);
                var c = DiffRect(b, bandLimits.Length);

                var d = timecomb(c, 2, 60, 230, bandLimits, maxFreq);
                var e = timecomb(c, 0.5, d - 2, d + 2, bandLimits, maxFreq);
                var f = timecomb(c, 0.1, e - 0.5, e + 0.5, bandLimits, maxFreq);
                var g = timecomb(c, 0.01, f - 0.1, f + 0.1, bandLimits, maxFreq);

                int bmpMax = (int)g;

                _trackInfo.Tempo.SetValueForInterval(bmpMax, new TimeInterval(new TimeSpan(0), _GetCurrentTimeCallback()));

                _calculated = true;
            }
        }

        private double timecomb(Complex[][] sig, double acc, double minbmp, double maxbmp, int[] bandLimits, int maxFreq)
        {
            var n = sig[0].Length;

            var nbands=bandLimits.Length;

            var npulses = 3;

            Complex[][] dft = new Complex[nbands][];
            for (int i = 0; i < nbands; i++)
            {
                FastFourierTransform.FFT(true, (int)Math.Log(sig[i].Length, 2), sig[i]);
                dft[i] = sig[i];
            }

            var maxe = 0.0;
            var sbmp = 0.0;
            double bmp = minbmp;
            while (bmp <= (maxbmp + acc / 2))
            {
                var e = 0.0;
                var fill = new Complex[n];

                var nstep = (int)Math.Floor(120.0/bmp*maxFreq);
                var percent_done = 100*(bmp - minbmp)/(maxbmp - minbmp);
               
                for (int i = 0; i < npulses - 1; i++)
                {
                    fill[i*nstep].X = 1;
                }

                FastFourierTransform.FFT(true, (int)Math.Log(fill.Length, 2), fill);
                
                for (int i = 0; i < nbands; i++)
                {
                    for (int j = 0; j < fill.Length; j++)
                    {
                        var x = fill[j].Multiply(dft[i][j]).Abs();
                        e += x*x;
                    }
                }

                if (e > maxe)
                {
                    sbmp = bmp;
                    maxe = e;
                }

                bmp += acc;
            }
            

            return sbmp;

        }

        private Complex[][] DiffRect(Complex[][] sig, int nbands)
        {
            var n = sig[0].Length;
            var output = new Complex[nbands][]; // zeros(n,nbands);
            for (int i = 0; i < nbands; i++)
            {
                output[i] = new Complex[n];

                for (int j = 4; j < n; j++)
                {
                    var d = sig[i][j].X - sig[i][j - 1].X;
                    if (d > 0)
                    {
                        output[i][j].X = d;
                    }
                }
            }

            return output;
        }

        private Complex[][] HWindow(Complex[][] sig, double winlength, int[] bandlimits, int maxfreq)
        {
            var n = sig[0].Length;
            var nbands = bandlimits.Length;

            var hannlen = (int) (winlength*2*maxfreq);

            var hann = new Complex[hannlen];

            for (int i = 0; i < hannlen; i++)
            {
                var a = (float) Math.Cos(i*Math.PI/hannlen/2);
                hann[i].X = a*a;
            }

            var wave = new float[nbands][];
            for (int i = 0; i < nbands; i++)
            {
                FastFourierTransform.FFT(false, (int) Math.Log(sig[i].Length, 2), sig[i]);

                wave[i] = sig[i].Select(x => x.X).ToArray();
            }

            var freq = new Complex[nbands][];
            for (int i = 0; i < nbands; i++)
            {
                freq[i] = new Complex[n];
                for (int j = 0; j < n; j++)
                {
                    if (wave[i][j] < 0)
                    {
                        wave[i][j] = -wave[i][j];
                    }

                    freq[i][j].X = wave[i][j];
                }

                FastFourierTransform.FFT(true, (int) Math.Log(freq[i].Length, 2), freq[i]);
            }

            FastFourierTransform.FFT(true, (int) Math.Log(hann.Length, 2), hann);

            for (int i = 0; i < nbands; i++)
            {
                for (int j = 0; j < freq[i].Length; j++)
                {
                    freq[i][j] = freq[i][j].Multiply(hann[j % hannlen]);
                }

                FastFourierTransform.FFT(false, (int) Math.Log(freq[i].Length, 2), freq[i]);
            }

            var output = new Complex[nbands][]; // zeros(n,nbands);
            for (int i = 0; i < nbands; i++)
            {
                output[i] = freq[i];
            }

            return output;
        }

        private Complex[][] FilterBank(List<Tuple<float, float>> samples, int[] bandLimits, int maxFreq)
        {
            Complex[] dft = new Complex[samples.Count];
            for (int i = 0; i < dft.Length; i++)
            {
                dft[i].X = (float) (samples[i].Item1*FastFourierTransform.HammingWindow(i, dft.Length));
                dft[i].Y = 0;
            }

            FastFourierTransform.FFT(true, (int) Math.Log(samples.Count, 2), dft);

            int n = dft.Length;
            List<int> bl = new List<int>();
            List<int> br = new List<int>();
            for (int i = 0; i < bandLimits.Length - 1; i++)
            {
                bl.Add((int)(Math.Floor( 1.0 * bandLimits[i] / maxFreq * n / 2)) + 1);
                br.Add((int)(Math.Floor( 1.0 * bandLimits[i + 1] / maxFreq * n / 2)));
            }

            bl.Add(((int)(Math.Floor( 1.0 * bandLimits.Last() / maxFreq * n / 2)) + 1));
            br.Add((int)Math.Floor(1.0 * n / 2));

            var output = new Complex[bandLimits.Length][]; // zeros(n,nbands);
            for (int i = 0; i< bandLimits.Length; i++)
            {
                output[i] = new Complex[n];

                for (int j = bl[i]; j <= br[i]; j++)
                {
                    output[i][j] = dft[j];
                }

                for (int j = n + 1 - br[i]; j < n + 1 - bl[i]; j++)
                {
                    output[i][j] = new Complex();
                }
            }

            return output;
        }

        public override void OnTrackStopped()
        {
            
        }

        internal class SubbandEnergy
        {
            public double E { get; set; }

            public int BMP { get; set; }
        }

    }
}


//using System;
//using System.Collections.Generic;
//using System.Data;
//using System.Linq;
//using NAudio.Dsp;

//namespace Orpheus.Analyzer
//{
//    public class CombFilterWithBandsTempoAnalyzer: TrackAnalyserBase
//    {
//        private List<Tuple<float, float>> samples;
//        private bool _calculated = false;

//        public CombFilterWithBandsTempoAnalyzer(TrackInfo trackInfo, GetCurrentTimeDelegate getCurrentTimeCallback) 
//            : base(trackInfo, getCurrentTimeCallback)
//        {
//            samples = new List<Tuple<float, float>>();
//            _trackInfo.Tempo = new TimeStepFunction<double>();
//        }

//        bool IsPowerOfTwo(int x)
//        {
//            return (x & (x - 1)) == 0;
//        }

//        public override void ProcessSamples(float left, float right)
//        {
//            if (left > 1f || right > 1f)
//            {
//                throw new InvalidConstraintException();
//            }

//            samples.Add(new Tuple<float, float>(left, right));
//            int m = (int) Math.Log(samples.Count, 2);
//            if (_GetCurrentTimeCallback().TotalSeconds > 5  && IsPowerOfTwo(samples.Count) 
//                && samples.Count % 16 == 0 && !_calculated)
//            {
//                int N = samples.Count;
//                Complex[] fft = new Complex[N];
//                for (int i = 0; i < N; i++)
//                {
//                    fft[i].X = (float)(samples[i].Item1 * FastFourierTransform.HammingWindow(i, fft.Length));
//                    fft[i].Y = 0;//(float)(samples[i].Item2 * FastFourierTransform.HammingWindow(i, fft.Length));
//                }

//                FastFourierTransform.FFT(true, m, fft);

//                List<SubbandEnergy> subbandEnergies = new List<SubbandEnergy>();
//                for (int i = 0; i < 16; i++)
//                {
//                    subbandEnergies.Add(new SubbandEnergy());
//                }
//                for (int bmp = 40; bmp < 200; bmp += 10)
//                {
//                    int Ti = (int)(60.0/bmp*44100);

//                    int Ni = N/16;
//                    float[] l = new float[Ni];
//                    float[] j = new float[Ni];
//                    for (int k = 0; k < Ni; k++)
//                    {
//                        if ((k % Ti) == 0)//(Math.Abs((k % Ti) - 0) < 0.001)
//                            l[k] = j[k] = (float)Math.Pow(2, 15);
//                        else
//                            l[k] = j[k] = 0;
//                    }

//                    Complex[] fftImpulses = new Complex[Ni];
//                    for (int i = 0; i < Ni; i++)
//                    {
//                        fftImpulses[i].X = (float)(l[i] * FastFourierTransform.HammingWindow(i, fftImpulses.Length));
//                        fftImpulses[i].Y = 0;// (float)(j[i] * FastFourierTransform.HammingWindow(i, fft.Length));
//                    }

//                    int mi = (int)Math.Log(Ni, 2);
//                    FastFourierTransform.FFT(true, mi, fftImpulses);

//                    int ws = N/16;
//                    for (int s = 0; s < 16; s++)
//                    {
//                        double E = 0;

//                        for (int i = 0; i < ws; i++)
//                        {
//                            E += fft[i + ws * s].Multiply(fftImpulses[i]).Abs();
//                        }

//                        if (E > subbandEnergies[s].E)
//                        {
//                            subbandEnergies[s].E = E;
//                            subbandEnergies[s].BMP = bmp;
//                        }
//                    }
//                }

//                int bmpMax = (int)(subbandEnergies.Sum(se => se.E*se.BMP)/subbandEnergies.Sum(se => se.E));

//                _trackInfo.Tempo.SetValueForInterval(bmpMax, new TimeInterval(new TimeSpan(0), _GetCurrentTimeCallback()));

//                _calculated = true;
//            }
//        }

//        public override void OnTrackStopped()
//        {
            
//        }

//        internal class SubbandEnergy
//        {
//            public double E { get; set; }

//            public int BMP { get; set; }
//        }

//    }
//}
